跳到主要内容

MySQL 学习(12)约束与触发器

数据完整性约束

关系型数据库系统和文件系统的一个不同点是,关系数据库本身能保证存储数据的完整性,不需要应用程序的控制,而文件系统一般需要在程序端进行控制。

当前几乎所有的关系型数据库都提供了约束(constraint)机制,该机制提供了一条强大而简易的途径来保证数据库中数据的完整性。

一般来说,数据完整性有以下三种形式:

1、实体完整性 保证表中有一个主键。在 InnoDB 存储引擎表中,用户可以通过定义 Primary Key 或 Unique Key 约束来保证实体的完整性。用户还可以通过编写一个触发器来保证数据完整性。

2、域完整性 保证数据每列的值满足特定的条件。在 InnoDB 存储引擎表中,域完整性可以通过以下几种途径来保证:

  • 选择合适的数据类型确保一个数据值满足特定条件。
  • 外键(Foreign Key)约束。
  • 编写触发器。
  • 还可以考虑用 DEFAULT 约束作为强制域完整性的一个方面。

3、参照完整性 保证两张表之间的关系。InnoDB 存储引擎支持外键,因此允许用户定义外键以强制参照完整性,也可以通过编写触发器以强制执行。

对于 InnoDB 存储引擎本身而言,提供了以下几种约束:

  • Primary Key
  • Unique Key
  • Foreign Key
  • Default
  • NOT NULL

约束的建立与查找

约束的创建可以采用以下两种方式:

  • 表建立时就进行约束定义
  • 利用 ALTER TABLE 命令来进行创建约束

对 Unique Key(唯一索引)的约束,用户还可以通过命令 CREATE UNIQUE INDEX 来建立。对于主键约束而言,其默认约束名为 PRIMARY。而对于 Unique Key 约束而言,默认约束名和列名一样,当然也可以人为指定 Unique Key 约束的名字。

Foreign Key 约束似乎会有一个比较神秘的默认名称。

下面是一个简单的创建表的语句,表上有一个主键和一个唯一键:

create table t_user(
id int(10),
name varchar(32),
primary key(id),
unique key(name)
);

具体如何创建就不讲了~

索引和约束的区别

在前面的小节中已经看到 Primary Key 和 Unique Key 的约束,有人不禁会问:这不就是通常创建索引的方法吗?那约束和索引有什么区别呢?

的确,当用户创建了一个唯一索引就创建了一个唯一的约束。

但是约束和索引的概念还是有所不同的,约束更是一个逻辑的概念,用来保证数据的完整性,而索引是一个数据结构,既有逻辑上的概念,在数据库中还代表着物理存储的方式。

对错误数据的约束

在某些默认设置下,MySQL 数据库允许非法的或不正确的数据的插人或更新,又或者可以在数据库内部将其转化为一个合法的值,如向 NOT NULL 的字段插入一个 NULL 值,MySQL 数据库会将其更改为 0 再进行插人,因此数据库本身没有对数据的正确性进行约束。

例如:

create table a
(
id int not null,
data Date not null
)

-- 如果插入非 date 的数据进去它并不会报错,只会抛出警告
-- 如果想要报错而不是警告得使用 sql_mode 参数来严格审核输入的参数

set sql_mode = 'STRICT_TRANS_TABLES';

ENUM 和 SET 约束

MySQL 数据库不支持传统的 CHECK 约束,但是通过 ENUM 和 SET 类型可以解决部分这样的约束需求。

例如表上有一个性别类型,规定域的范围只能是 male 或 female,在这种情况下用户可以通过 ENUM 类型来进行约束。

CREATE TABLE a (
id INT,
sex ENUM('male', 'female')
);

INSERT INTO a SELECT 1, 'female';

INSERT INTO a SELECT 2, 'bi';

可以看到,在上述例子中对第二条记录的插人依然是报了警告。因此如果想实现 CHECK 约束,还需要配合设置参数 sql_mode

SET sql_mode = 'STRICT_TRANS_TABLES';

这次对非法的输入值进行了约束,但是只限于对离散数值的约束,对于传统 CHECK 约束支持的连续值的范围约束或更复杂的约束,ENUMSET 类型还是无能为力,这时用户需要通过触发器来实现对于值域的约束。

触发器与约束器

通过前面小节的介绍,用户已经知道完整性约束通常也可以使用触发器来实现,因此在了解数据完整性前先对触发器来做一个了解。

触发器的作用是在执行 INSERTDELETEUPDATE 命令之前或之后自动调用 SQL 命令或存储过程。MySQL 5.0 对触发器的实现还不是非常完善,限制比较多,而从 MySQL 5.1 开始触发器已经相对稳定,功能也较之前有了大幅的提高。

创建触发器的命令是 CREATE TRIGGER,只有具备 Super 权限的 MySQL 数据库用户才可以执行这条命令:

CREATE <触发器名> < BEFORE | AFTER >
<INSERT | UPDATE | DELETE >
ON <表名> FOR EACH Row<触发器主体>

最多可以为一个表建立 6 个触发器,即分别为 INSERT、UPDATE、DELETE 的 BEFORE 和 AFTER 各定义一个。BEFORE 和 AFTER 代表触发器发生的时间,表示是在每行操作的之前发生还是之后发生。

当前 MySQL 数据库只支持 FOR EACH ROW 的触发方式,即按每行记录进行触发,不支持像 DB2 的 FOR EACH STATEMENT 的触发方式。

通过触发器,用户可以实现 MySQL 数据库本身并不支持的一些特性,如对于传统 CHECK 约束的支持,物化视图、高级复制、审计等特性。这里先关注触发器对于约束的支持。

示例:创建学生信息表 tb_student 时,将年龄(age)的值设置在 1 至 100 之间的数值。

-- 学生信息表
CREATE TABLE tb_student
(
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(30),
age INT NOT NULL CHECK(age>0 AND age<100)
);

上述示例中,虽然给年龄(age)字段设置了 CHECK 检查约束,但是仍然可以往该数据表中添加不符合要求的年龄数组(原因上面说了),例如:-1 或者 120。

解决方法:使用触发器实现 CHECK 检查约束的功能。

1、创建 tb_student(学生信息表)。

-- 创建学生信息表
CREATE TABLE tb_student
(
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(30),
age INT NOT NULL
);

2、创建检查年龄(age)字段是否有效的触发器。

-- 创建触发器
CREATE TRIGGER trg_tb_student_insert_check BEFORE INSERT
ON tb_student FOR EACH ROW
BEGIN
DECLARE msg varchar(100);

IF NEW.age <= 0 OR NEW.age >= 100
THEN
SET msg = CONCAT('您输入的年龄值:',NEW.age,' 为无效的年龄,请输入0到100以内的有效数字。');
SIGNAL SQLSTATE 'HY000' SET MESSAGE_TEXT = msg;
END IF;
END;

3、编写测试语句。

INSERT INTO tb_student(name,age) VALUES('测试数据~',120);

这时就会抛出错误

外键约束

外键用来保证参照完整性,MySQL 数据库的 MyISAM 存储引擎本身并不支持外键,对于外键的定义只是起到一个注释的作用。而 InnoDB 存储引擎则完整支持外键约束。

一般来说,称被引用的表为父表,引用的表称为子表。外键定义时的 ON DELETEON UPDATE 表示在对父表进行 DELETE 和 UPDATE 操作时,对子表所做的操作,可定义的子表操作有:

  • CASCADE
  • SET NULL
  • NO ACTION
  • RESTRICT

1、CASCADE 表示当父表发生 DELETEUPDATE 操作时,对相应的子表中的数据也进行 DELETEUPDATE 操作。SET NULL 表示当父表发生 DELETEUPDATE 操作时,相应的子表中的数据被更新为 NULL 值,但是子表中相对应的列必须允许为 NULL 值。NO ACTION 表示当父表发生 DELETEUPDATE 操作时,抛出错误,不允许这类操作发生。

2、RESTRICT 表示当父表发生 DELETEUPDATE 操作时,抛出错误,不允许这类操作发生。如果定义外键时没有指定 ON DELETEON UPDATERESTRICT 就是默认的外键设置。

在其他数据库中,如 Oracle 数据库,有一种称为延时检查 deferred check 的外键约束,即检查在 SQL 语句运行完成后再进行。而目前 MySQL 数据库的外键约束都是即时检查(immediatecheck)

因此从上面的定义可以看出,在 MySQL 数据库中 NO ACTIONRESTRICT 的功能是相同的。

对于参照完整性约束,外键能起到一个非常好的作用。但是 对于数据的导人操作时,外键往往导致在外键约束的检查上花费大量时间。

因为 MySQL 数据库的外键是即时检查的,所以对导入的每一行都会进行外键检查。但是用户可以在导人过程中忽视外键的检查,如:

-- 导入大量数据前先关闭外键检查
SET foreign_key_checks = 0;
LOAD Data ...
-- 导入后再开启
SET foreign_key_checks = 1;